package com.linkgie.galaxyframework.utils.json;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.regex.Pattern;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonParser.NumberType;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.linkgie.galaxyframework.utils.CollectionUtils;
import com.linkgie.galaxyframework.utils.TextUtils;

/**
 * JSON 工具类；
 */
public class JSONUtils {

	/**
	 * 数字的正则表达式；
	 */
	private static final String NUMBER_REGEX = "^-?\\d+(\\.\\d+)?$";
	private static final Pattern NUMBER_PATTERN = Pattern.compile(NUMBER_REGEX);

	/**
	 * 字符串的正则表达式；
	 */
	private static final String STRING_REGEX = "^\\s*((\"[^\"]*\")|('[^\'\"]'))\\s*$";
	private static final Pattern STRING_PATTERN = Pattern.compile(STRING_REGEX);

	/**
	 * 反序列化时对于声明类型为 Object 的属性或对象，实际返回的具体类型；
	 */
	public static Class<?> OBJECT_CONCRETE_TYPE = JsonElement.class;

	private static final JsonContext CONTEXT = new JsonContext();

	private static final SimpleModule serializerModule = new SimpleModule();;

	static {

		CONTEXT.mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
		CONTEXT.mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
		CONTEXT.mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
		// mapper.configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true);
		CONTEXT.mapper.configure(JsonReadFeature.ALLOW_TRAILING_COMMA.mappedFeature(), true);
		CONTEXT.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		CONTEXT.mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

		CONTEXT.mapper.setSerializationInclusion(Include.NON_NULL);

		
		serializerModule.addSerializer(JsonProxy.class, new ProxySerializer<>(JsonProxy.class));
		// 针对 JSON 转换目标为 Object 类型的情形进行反序列化，返回 {@link JsonElement} ；
		serializerModule.addDeserializer(Object.class, new ObjectDeserializer());
		CONTEXT.mapper.registerModule(serializerModule);

		// 使基于此 mapper 的所有输出都采用 pretty 格式；
		// pretty_mapper.enable(SerializationFeature.INDENT_OUTPUT);
	}

	/**
	 * 同步上下文的 JSON 配置给指定的配置器；
	 * 
	 * @param jsonConfigurer
	 */
	public static void useConfigurtion(Consumer<Object> jsonConfigurer) {
		jsonConfigurer.accept(CONTEXT.mapper);
	}

	public static <T> void addSerialize(Class<T> type, JsonSerialize<T> serializer){
		serializerModule.addSerializer(type, new SerializeDelegator<T>(type, serializer));
	}

	public static <T> String toJSON(Object value, Class<T> type) {

		try {
			return CONTEXT.mapper.writerFor(type).writeValueAsString(value);
		} catch (JsonProcessingException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	public static <T> String toJSON(Object value, Class<T> type, boolean pretty) {

		try {
			if (pretty) {
				return CONTEXT.mapper.writerFor(type).withDefaultPrettyPrinter().writeValueAsString(value);
			}
			return CONTEXT.mapper.writerFor(type).writeValueAsString(value);
		} catch (JsonProcessingException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	public static <T> String toJSON(Object value) {
		return toJSON(value, false);
	}

	public static <T> String toJSON(Object value, boolean pretty) {
		try {
			if (value instanceof JsonWrapper) {
				return ((JsonWrapper) value).node.toString();
			}
			if (value.getClass() == JsonElement[].class) {
				JsonElement[] elements = (JsonElement[]) value;
				ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode(elements.length);
				for (JsonElement jsonElement : elements) {
					arrayNode.add(((JsonWrapper) jsonElement).node);
				}
				return pretty ? arrayNode.toPrettyString() : arrayNode.toString();
			}
			if (pretty) {
				return CONTEXT.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
			}
			return CONTEXT.mapper.writeValueAsString(value);
		} catch (JsonProcessingException e) {
			throw new JsonException(e.getMessage(), e);
		}
	}

	/**
	 * 判断是否是 JSON 字符；
	 * <p>
	 * 
	 * 此方法判断的JSON 字符的方法是检查指定的字符是否由花括号 { } 或者方括号 [ ] 或者双引号 ""包围；
	 * <p>
	 * 
	 * 这只是一种简化但不严谨的方法，检查通过返回 true 也不代表在花括号 { } 或者方括号 [ ] 或者双引号 "" 之间的内容符合 JSON 语法；
	 * 
	 * @param str
	 * @return
	 */
	public static boolean isJSON(String str) {
		return str != null && (isJSONObject(str) || isJSONOArray(str) || isJSONOValue(str));
	}

	/**
	 * 判断是否是 JSON 对象；
	 * <p>
	 * 
	 * 此方法判断的JSON 对象的方法是检查指定的字符是否由花括号 { } 包围；
	 * <p>
	 * 
	 * 这只是一种简化但不严谨的方法，检查通过返回 true 也不代表在花括号 { } 之间的内容符合 JSON 语法；
	 * 
	 * @param str
	 * @return
	 */
	public static boolean isJSONObject(String str) {
		return str != null && TextUtils.startswithIgnoreWhitespace(str, '{')
				&& TextUtils.endswithIgnoreWhitespace(str, '}');
	}

	/**
	 * 判断是否是 JSON 数组；
	 * <p>
	 * 
	 * 此方法判断的JSON 数组的方法是检查指定的字符是否由方括号 [ ] 包围；
	 * <p>
	 * 
	 * 这只是一种简化但不严谨的方法，检查通过返回 true 也不代表在方括号 [ ] 之间的内容符合 JSON 语法；
	 * 
	 * @param str
	 * @return
	 */
	public static boolean isJSONOArray(String str) {
		return str != null && TextUtils.startswithIgnoreWhitespace(str, '[')
				&& TextUtils.endswithIgnoreWhitespace(str, ']');
	}

	/**
	 * 判断是否是 JSON 单值元素；
	 * <p>
	 * 包括 字符串、数字、布尔值；
	 * <p>
	 * 
	 * 这只是一种简化、快速但不严谨的方法，检查通过字符串的边界特征判断，但并未进行全面的内容检查，在转换为 JsonElement
	 * 的过程中仍然可能出现语法问题；
	 * 
	 * @param str
	 * @return
	 * @throws IOException
	 * @throws JsonParseException
	 */
	public static boolean isJSONOValue(String str) throws JsonFormatException {
		return str != null && (isJSONBoolean(str) || isJSONNumber(str) || isJSONString(str));
	}

	public static boolean isJSONBoolean(String value) {
		return TextUtils.literalEquals(value, "true", true) || TextUtils.literalEquals(value, "false", true);
	}

	public static boolean isJSONNumber(String value) throws JsonFormatException {
		return value != null && NUMBER_PATTERN.matcher(value).matches();
	}

	public static boolean isJSONString(String value) throws JsonFormatException {
		if (value == null) {
			return false;
		}
		int startIndex = TextUtils.indexOfNonWhitespace(value);
		if (startIndex == -1) {
			return false;
		}
		int endIndex = TextUtils.lastIndexOfNonWhitespace(value);
		int length = endIndex - startIndex + 1;
		// 要以 "" 或者 '' 包裹，必须至少长度为 2 ；
		if (length < 2) {
			return false;
		}
		char first = value.charAt(startIndex);
		char last = value.charAt(endIndex);
		if (first == '"' && last == '"') {
			// 以 "" 包裹；
			// 检查中间没有任何双引号；
			int idx = value.indexOf('"', startIndex + 1);
			// 预期至少会匹配到结尾的双引号；
			if (idx == endIndex) {
				return true;
			}
		}
		if (first == '\'' && last == '\'') {
			// 以 '' 包裹；
			// 检查中间没有任何双引号和单引号；
			int idx = value.indexOf('"', startIndex + 1);
			if (idx > -1) {
				// 存在双引号；
				return false;
			}

			idx = value.indexOf('\'', startIndex + 1);
			// 预期至少会匹配到结尾的双引号；
			if (idx == endIndex) {
				return true;
			}
		}
		return false;
	}

	public static JsonElement toValue(String json) throws JsonFormatException, JsonException {
		if (json == null) {
			return null;
		}
		try {
			JsonNode treeNode = CONTEXT.mapper.readTree(json);
			return new JsonWrapper(treeNode, CONTEXT);
		} catch (JsonParseException e) {
			throw new JsonFormatException(e.getMessage(), e);
		} catch (JsonProcessingException e) {
			throw new JsonException(e.getMessage(), e);
		}
	}

	/**
	 * 反序列化；
	 * 
	 * 如果指定的类型是接口，则返回结果是一个实现了指定接口的动态代理对象；
	 * 
	 * @param <T>
	 * @param json
	 * @param type
	 * @return
	 */
	public static <T> T toValue(String json, Class<T> type) throws JsonFormatException, JsonException {
		return CONTEXT.toValue(json, type);
	}

	public static <T> void write(Object value, File file) {
		write(value, "UTF-8", file);
	}

	public static <T> void write(Object value, String charset, File file) {
		write(value, null, false, charset, file);
	}

	public static void write(Object value, Class<?> type, String charset, File file) {
		write(value, type, false, charset, file);
	}

	public static void write(Object value, Class<?> type, File file) {
		write(value, type, false, "UTF-8", file);
	}

	public static void write(Object value, Class<?> type, boolean pretty, File file) {
		write(value, type, pretty, "UTF-8", file);
	}

	public static <T> void write(Object value, Class<?> type, boolean pretty, String charset, File file) {
		try (FileOutputStream output = new FileOutputStream(file);
				OutputStreamWriter writer = new OutputStreamWriter(output, charset)) {
			write(value, type, pretty, writer);
		} catch (IOException e) {
			throw new JsonException(e.getMessage(), e);
		}
	}

	public static <T> void write(Object value, Writer writer) {
		write(value, false, writer);
	}

	public static <T> void write(Object value, boolean pretty, Writer writer) {
		write(value, null, pretty, writer);
	}

	public static <T> void write(Object value, OutputStream output) {
		write(value, false, output);
	}

	public static <T> void write(Object value, boolean pretty, OutputStream output) {
		write(value, null, pretty, output);
	}

	public static <T> void write(Object value, Class<?> type, boolean pretty, Writer writer) {
		try {
			ObjectWriter objectWriter = forOptions(type, pretty);

			objectWriter.writeValue(writer, value);
		} catch (JsonProcessingException e) {
			throw new IllegalStateException(e.getMessage(), e);
		} catch (IOException e) {
			throw new JsonException(e.getMessage(), e);
		}
	}

	public static <T> void write(Object value, Class<?> type, boolean pretty, OutputStream output) {
		try {
			ObjectWriter objectWriter = forOptions(type, pretty);
			objectWriter.writeValue(output, value);

		} catch (JsonProcessingException e) {
			throw new IllegalStateException(e.getMessage(), e);
		} catch (IOException e) {
			throw new JsonException(e.getMessage(), e);
		}
	}

	private static <T> ObjectWriter forOptions(Class<?> type, boolean pretty) {
		ObjectWriter objectWriter;
		if (pretty) {
			objectWriter = CONTEXT.mapper.writerWithDefaultPrettyPrinter();
		} else {
			objectWriter = CONTEXT.mapper.writer();
		}
		if (type != null) {
			objectWriter = objectWriter.forType(type);
		}
		return objectWriter;
	}

	private static class JsonContext {

		private final ObjectMapper mapper = new ObjectMapper();

		private final Map<Class<?>, ProxyDeserializer<?>> interfaceDeserializers = new ConcurrentHashMap<>();

		@SuppressWarnings("unchecked")
		public <T> T toValue(String json, Class<T> type) throws JsonFormatException, JsonException {
			if (json == null) {
				return null;
			}
			try {
				if (type == JsonNode.class) {
					return (T) mapper.readTree(json);
				}
				if (type == JsonElement.class) {
					return (T) toJsonElement(json);
				}
				if (type == JsonElement[].class) {
					return (T) toJsonArray(json);
				}
				// 针对 JsonElement 之外的接口类型进行处理；
				prepareForInterface(type);

				return mapper.readValue(json, type);
			} catch (JsonParseException e) {
				throw new JsonFormatException(e.getMessage(), e);
			} catch (JsonProcessingException e) {
				throw new JsonException(e.getMessage(), e);
			}
		}

		@SuppressWarnings("unchecked")
		private <T> void prepareForInterface(Class<T> type) {
			if (type.isInterface() || (type.isArray() && type.getComponentType().isInterface())) {
				// 如果接口未
				Class<?> intf = type;
				if (type.isArray()) {
					intf = type.getComponentType();
				}

				if (!interfaceDeserializers.containsKey(intf)) {
					ProxyDeserializer<?> deserializer = new ProxyDeserializer<>(intf, mapper);

					SimpleModule interfaceDesModule = new SimpleModule();
					interfaceDesModule.addDeserializer((Class<Object>) intf, deserializer);

					mapper.registerModule(interfaceDesModule);
					interfaceDeserializers.put(intf, deserializer);
				}
			}
		}

		private JsonElement[] toJsonArray(String json) throws JsonProcessingException, JsonMappingException {
			JsonNode jsonNode = mapper.readTree(json);
			return toJsonArray(jsonNode);
		}

		private JsonElement[] toJsonArray(JsonNode jsonNode) throws JsonProcessingException, JsonMappingException {
			if (!jsonNode.isArray()) {
				throw new IllegalArgumentException("The specified json is not an array!");
			}
			int count = jsonNode.size();
			JsonElement[] jsonArray = new JsonElement[count];
			for (int i = 0; i < count; i++) {
				JsonNode element = jsonNode.get(i);
				if (element == null) {
					jsonArray[i] = null;
				} else {
					jsonArray[i] = new JsonWrapper(element, CONTEXT);
				}
			}
			return jsonArray;
		}

		private JsonElement toJsonElement(String json) throws JsonProcessingException, JsonMappingException {
			JsonNode jsonNode = mapper.readTree(json);
			return new JsonWrapper(jsonNode, this);
		}

		@SuppressWarnings("unchecked")
		public <T> T toValue(JsonNode json, Class<T> type) throws JsonFormatException, JsonException {
			try {
				if (type == JsonElement.class) {
					return (T) new JsonWrapper(json, this);
				}
				if (type == JsonElement[].class) {
					return (T) toJsonArray(json);
				}
				// 针对 JsonElement 之外的接口类型进行处理；
				prepareForInterface(type);

				return mapper.treeToValue(json, type);
			} catch (JsonParseException e) {
				throw new JsonFormatException(e.getMessage(), e);
			} catch (JsonProcessingException e) {
				throw new JsonException(e.getMessage(), e);
			}
		}

	}

	private static class JsonWrapper implements JsonElement {

		private JsonContext context;

		private JsonNode node;

		/**
		 * @param jsonNode
		 * @param context
		 */
		public JsonWrapper(JsonNode jsonNode, JsonContext context) {
			this.node = jsonNode;
			this.context = context;
		}

		@Override
		public boolean hasField(String fieldName) {
			return node.has(fieldName);
		}

		@Override
		public boolean isObject() {
			return node.isObject();
		}

		@Override
		public boolean isArray() {
			return node.isArray();
		}

		@Override
		public String toJson() {
			return node.toString();
		}

		@Override
		public String toPrettyJson() {
			return node.toPrettyString();
		}

		@SuppressWarnings("unchecked")
		@Override
		public <T> T toValue(Class<T> type) throws JsonFormatException, JsonException {
			if (type == JsonElement.class) {
				return (T) this;
			}
			return context.toValue(node, type);
		}

		@Override
		public String toString() {
			return this.toJson();
		}

		@Override
		public int hashCode() {
			return Objects.hashCode(node);
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == this) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (obj instanceof JsonWrapper) {
				JsonWrapper other = (JsonWrapper) obj;
				return this.node.equals(other.node);
			}
			return false;
		}

		@Override
		public JsonElement getField(String fieldName) {
			JsonNode fieldNode = node.get(fieldName);
			return new JsonWrapper(fieldNode, CONTEXT);
		}
		

		@Override
		public String getString(String fieldName) {
			JsonNode value = node.get(fieldName);
			return value == null ? null : value.textValue();
		}

		@Override
		public Integer getInt(String fieldName) {
			JsonNode value = node.get(fieldName);
			return value == null ? null : (value.isInt() ? value.intValue() : null);
		}

		@Override
		public Long getLong(String fieldName) {
			JsonNode value = node.get(fieldName);
			return value == null ? null : (value.isLong() ? value.longValue() : null);
		}

		@Override
		public Boolean getBoolean(String fieldName) {
			JsonNode value = node.get(fieldName);
			return value == null ? null : (value.isBoolean() ? value.booleanValue() : null);
		}

		@SuppressWarnings("unchecked")
		@Override
		public <T> T getValue(String fieldName, Class<T> type) {
			JsonNode value = node.get(fieldName);
			if (value == null) {
				return null;
			}
			if (type == JsonNode.class) {
				return (T)value;
			}
			if (type == JsonElement.class) {
				return (T)new JsonWrapper(value, this.context);
			}
			return this.context.toValue(value, type);
		}

		@Override
		public JsonElement array(int index) {
			JsonNode value = node.isArray() ? node.get(index) : null;
			return value == null ? null : new JsonWrapper(value, this.context);
		}

		@Override
		public Iterator<JsonElement> arrayIterator() {
			Iterator<JsonNode> iterator = node.isArray() ? node.iterator() : null;
			return iterator == null ? null : CollectionUtils.cast(iterator, (item) -> new JsonWrapper(item, this.context));
		}

		@Override
		public Double getDouble(String fieldName) {
			JsonNode value = node.get(fieldName);
			return value == null ? null : (value.isDouble() ? value.doubleValue() : null);
		}
	}

	private static class ObjectDeserializer extends JsonDeserializer<Object> {

		@Override
		public Object deserialize(JsonParser parser, DeserializationContext desContext)
				throws IOException, JacksonException {
			JsonToken jsonToken = parser.currentToken();
			if (jsonToken == JsonToken.VALUE_NULL) {
				return null;
			} else if (jsonToken == JsonToken.VALUE_TRUE || jsonToken == JsonToken.VALUE_FALSE) {
				return parser.getValueAsBoolean();
			} else if (jsonToken == JsonToken.VALUE_STRING) {
				return parser.getValueAsString();
			} else if (jsonToken == JsonToken.VALUE_NUMBER_INT || jsonToken == JsonToken.VALUE_NUMBER_FLOAT) {
				NumberType numberType = parser.getNumberType();
				if (numberType == NumberType.INT) {
					return parser.getValueAsInt();
				} else if (numberType == NumberType.LONG) {
					return parser.getValueAsLong();
				} else if (numberType == NumberType.FLOAT) {
					return parser.getFloatValue();
				} else if (numberType == NumberType.DOUBLE) {
					return parser.getValueAsDouble();
				} else if (numberType == NumberType.BIG_INTEGER) {
					return parser.getBigIntegerValue();
				} else if (numberType == NumberType.BIG_DECIMAL) {
					return parser.getDecimalValue();
				}
			}

			JsonNode jsonValue = parser.readValueAsTree();
			if (jsonValue == null) {
				return null;
			}
			return new JsonWrapper(jsonValue, CONTEXT);
		}

	}

}
